package com.zxhysy.log.annotation.advisor;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.zxhysy.log.annotation.context.*;
import com.zxhysy.log.annotation.entity.LogRecord;
import com.zxhysy.log.annotation.entity.LogOps;
import com.zxhysy.log.annotation.filter.JsonFilterUtil;
import com.zxhysy.log.annotation.service.IOperatorGetService;
import lombok.SneakyThrows;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * 字段值 SpEl 解析器
 *
 * @author zhangxinhong
 * @since 2022/1/26 18:04
 */
public class LogValueParser {

    private static final ObjectMapper objectMapper = new ObjectMapper();
    /**
     * 表达式解析
     */
    private final LogExpressionEvaluator expressionEvaluator = new LogExpressionEvaluator();
    /**
     * 操作人
     */
    private final IOperatorGetService operatorGetService;
    /**
     * 函数注册器
     */
    private final LogFuncRegister logFuncRegister;
    /**
     * 方法参数缓存
     */
    private final Map<Method, String[]> methodParams = new ConcurrentHashMap<>(64);


    public LogValueParser(IOperatorGetService operatorGetService, LogFuncRegister logFuncRegister) {
        this.operatorGetService = operatorGetService;
        this.logFuncRegister = logFuncRegister;
    }

    /**
     * 解析字段
     *
     * @param logRecord       日志注解数据
     * @param netParameter    网络参数
     * @param methodParameter 方法参数
     * @param methodResult    结果参数
     */
    public LogOps parse(LogRecord logRecord, NetParameter netParameter, MethodParameter methodParameter, MethodResult methodResult) {
        // 将参数注册到 el上下文
        LogEvaluationContext evaluationContext = expressionEvaluator.createEvaluationContext(methodParameter, methodParameter.getTargetClass(),
                methodParameter.getMethod(), methodParameter.getArgs());
        // 注册函数
        logFuncRegister.registerFunction(evaluationContext);

        LogOps logOps = new LogOps();
        logOps.setBizType(logRecord.getBizType());
        logOps.setLogType(logRecord.getLogType());

        // 开始进行 SpEl 表达式解析 内部判断是否为表达式
        logOps.setBizId(expressionEvaluator.condition(logRecord.getBizId(), methodParameter.getTargetClass(),
                methodParameter.getMethod(), evaluationContext));

        logOps.setDescription(expressionEvaluator.condition(logRecord.getDescription(), methodParameter.getTargetClass(),
                methodParameter.getMethod(), evaluationContext));

        logOps.setOperator(expressionEvaluator.condition(logRecord.getOperator(), methodParameter.getTargetClass(),
                methodParameter.getMethod(), evaluationContext));
        if (!StringUtils.hasText(logOps.getOperator())) {
            logOps.setOperator(operatorGetService.getOperator());
        }

        if (methodResult.isSuccess()) {
            logOps.setIsSuccess(1);
            logOps.setSuccess(expressionEvaluator.condition(logRecord.getSuccess(), methodParameter.getTargetClass(),
                    methodParameter.getMethod(), evaluationContext));
        } else {
            logOps.setIsSuccess(0);
            logOps.setFail(expressionEvaluator.condition(logRecord.getFail(), methodParameter.getTargetClass(),
                    methodParameter.getMethod(), evaluationContext));
        }

        if (netParameter != null) {
            logOps.setIp(netParameter.getIp());
            logOps.setUrl(netParameter.getUrl());
            logOps.setMethodType(netParameter.getMethodType());
        }

        logOps.setMethodName(methodParameter.getMethod().getName());
        logOps.setMethodParams(getParams(methodParameter, logRecord.getIgnore()));
        logOps.setCostTime(methodResult.getCostTime());
        logOps.setErrorMsg(methodResult.getErrorMsg());
        return logOps;
    }

    @SneakyThrows
    private String getParams(MethodParameter methodParameter, String[] ignoreMethodParamName) {
        String[] paramNames = methodParams.computeIfAbsent(methodParameter.getMethod(), method -> {
            ParameterNameDiscoverer paramNameDiscoverer = expressionEvaluator.getParamNameDiscoverer();
            return paramNameDiscoverer.getParameterNames(method);
        });
        Map<Integer, Set<Class<?>>> objIndexAndIgnoreObjAttr = new HashMap<>();
        Object[] args = methodParameter.getArgs();
        for (int i = 0; i < args.length; i++) {
            Set<Class<?>> objIgnoreAttr = LogIgnoreParser.getLogIgnoreClass(args[i]);
            if (!objIgnoreAttr.isEmpty()) {
                objIndexAndIgnoreObjAttr.put(i, objIgnoreAttr);
            }
        }

        Set<String> ignoreMethodParamNameSet = Arrays.stream(ignoreMethodParamName).collect(Collectors.toSet());
        Set<Integer> ignoreMethodParamIndexSet = methodParameter.methodParamsIndexIgnore();
        if (paramNames != null && paramNames.length > 0) {
            Map<String, String> paramsMap = new HashMap<>(args.length);
            for (int i = 0; i < args.length; ++i) {
                if (!ignoreMethodParamNameSet.contains(paramNames[i]) && !ignoreMethodParamIndexSet.contains(i)) {
                    if (objIndexAndIgnoreObjAttr.containsKey(i)) {
                        Set<Class<?>> ignoreObjAttr = objIndexAndIgnoreObjAttr.get(i);
                        ObjectMapper om = new ObjectMapper();
                        JsonFilterUtil.addCustomExceptFilterForMapper(om, ignoreObjAttr);
                        paramsMap.put(paramNames[i], om.writeValueAsString(args[i]));
                    } else {
                        paramsMap.put(paramNames[i], objectMapper.writeValueAsString(args[i]));
                    }
                }
            }
            return objectMapper.writeValueAsString(paramsMap);
        }
        return null;
    }

}
